from flask_wtf import FlaskForm,Form
from wtforms import StringField,SubmitField,IntegerField,TextAreaField,FileField,BooleanField,SelectField,FormField,FieldList,HiddenField
from wtforms.validators import InputRequired,Length,Regexp,EqualTo
from wtforms import ValidationError
from app.models import User,Dataset,FPFE,DataPreprocessor,AcousticModel,LexiconDict,LanguageModel,Decoder,PostProcessor
import os
import shutil
from consta import path_sep
from sqlalchemy import and_

class UserEditForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('用户名',validators=[InputRequired(),Length(1,7),Regexp('^[A-Za-z][A-Za-z0-9_.]*$',0,'用户名要以字母开头，只能包含字母、数字、.和_')])
    note_name=StringField('备注',validators=[Length(0,10)])
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(User.id!=int(self.id.data),User.name==field.data)}
        if User.query.filter(*filter).first():
            raise ValidationError('用户名已被使用')
        
class BTHostForm(FlaskForm):
    ip=StringField('ip地址',validators=[InputRequired(),Regexp('^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$|^localhost$',0,'IP地址非法')])
    port=IntegerField('端口号',validators=[InputRequired()])
    submit=SubmitField('保存')

class BTDeviceDescriptionForm(FlaskForm):
    description=TextAreaField('计算设备备注')
    submit=SubmitField('保存')

class DataSubsetForm(FlaskForm):
    subdir=StringField('相对于数据集目录的数据子集目录',validators=[Length(max=10)])
    trn_ext=StringField('转录文本文件扩展名，比如:.trn.txt',validators=[Length(max=10)])
    trn_dir=StringField('相对于数据集目录的转录文本文件所在的目录',validators=[Length(max=10)])
    trn_file=StringField('转录文本汇总文件名',validators=[Length(max=20)])
    line_no=IntegerField('转录文本在转录文本文件的行号（从0开始计数）',default=0)
    trn_file_format=SelectField('转录文本文件格式',choices=[('1','txt'),('2','json')])
    submit=SubmitField('保存')

    def validate_trn_file_format(self,field):
        if len(self.trn_file.data)>0:
            if (field.data==1 and not self.trn_file.data.endswith('txt')) or (field.data==2 and not self.trn_file.data.endswith('json')):
                raise ValidationError('给出的转录文本汇总文件名并没有按照其文件格式命名，请规范地确定文件的后缀名')

class DatasubsetForm(Form):
    subdir=StringField('相对于数据集目录的数据子集目录',validators=[Length(max=10)])
    trn_ext=StringField('转录文本文件扩展名，比如:.trn.txt',validators=[Length(max=10)])
    trn_dir=StringField('相对于数据集目录的转录文本文件所在的目录',validators=[Length(max=10)])
    trn_file=StringField('转录文本汇总文件名',validators=[Length(max=20)])
    line_no=IntegerField('转录文本在转录文本文件的行号（从0开始计数）',default=0)
    trn_file_format=SelectField('转录文本文件格式',choices=[('1','txt'),('2','json')])

    def validate_trn_file_format(self,field):
        if len(self.trn_file.data)>0:
            if (field.data==1 and not self.trn_file.data.endswith('txt')) or (field.data==2 and not self.trn_file.data.endswith('json')):
                raise ValidationError('给出的转录文本汇总文件名并没有按照其文件格式命名，请规范地确定文件的后缀名')

class DatasetForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,10)])
    description=TextAreaField('备注')
    path=StringField('路径',validators=[InputRequired()])
    wav_ext=StringField('语音文件扩展名，例如：.wav,.mp3',default='.wav',validators=[Length(1,20)])
    wav2trn_type_is_summary=BooleanField('语音的转录文本已被汇总',default='checked')
    train=FormField(DatasubsetForm,'训练集')
    is_subset_conf_same=BooleanField('验证集、测试集信息中除相对于数据集目录的数据子集目录，其他信息均与训练集相同',default=False)
    val=FormField(DatasubsetForm,'验证集')
    test=FormField(DatasubsetForm,'测试集')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(Dataset.id!=int(self.id.data),Dataset.name==field.data)}
        if Dataset.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的数据集，请更名')
        
    def validate_path(self,field):
        if not os.path.exists(field.data):
            raise ValidationError('数据集目录不存在')
        
        if self.train.subdir.data is not None and not os.path.exists(os.path.join(field.data,self.train.subdir.data)):
            raise ValidationError(f'{field.data}/{self.train.subdir.data}训练集目录不存在')
        if self.train.trn_dir.data is not None and not os.path.exists(os.path.join(field.data,self.train.trn_dir.data)):
            raise ValidationError('训练集的转录文本文件所在的目录不存在')
        if self.train.trn_file.data is not None and not os.path.exists(os.path.join(field.data,self.train.trn_dir.data,self.train.trn_file.data)):
            raise ValidationError('训练集的转录文本汇总文件不存在')
        
        if self.val.subdir.data is not None and not os.path.exists(os.path.join(field.data,self.val.subdir.data)):
            raise ValidationError('验证集目录不存在')
        if self.test.subdir.data is not None and not os.path.exists(os.path.join(field.data,self.val.subdir.data)):
            raise ValidationError('测试集目录不存在')
        if not self.is_subset_conf_same:
            if self.val.trn_dir.data is not None and not os.path.exists(os.path.join(field.data,self.val.trn_dir.data)):
                raise ValidationError('验证集的转录文本文件所在的目录不存在')
            if self.val.trn_file.data is not None and not os.path.exists(os.path.join(field.data,self.val.trn_dir.data,self.val.trn_file.data)):
                raise ValidationError('验证集的转录文本汇总文件不存在')
            if self.test.trn_dir.data is not None and not os.path.exists(os.path.join(field.data,self.test.trn_dir.data)):
                raise ValidationError('测试集的转录文本文件所在的目录不存在')
            if self.test.trn_file.data is not None and not os.path.exists(os.path.join(field.data,self.test.trn_dir.data,self.test.trn_file.data)):
                raise ValidationError('测试集的转录文本汇总文件不存在')
            
class FPFEForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,20)])
    typ=SelectField('类型',choices=[('0','前端处理器'),('1','特征提取器'),('2','前端处理器+特征提取器')])
    modulename=StringField('模块名，如果要填写文件路径，请用“/”分隔',validators=[InputRequired()])
    classname=StringField('模块的全局函数名',validators=[InputRequired(),Length(1,50)])
    parameters=TextAreaField('全局函数的参数列表，请用“sample_rate”表示采样率的数值，系统会自动替换形参的语音文件采样率值')
    attribute=TextAreaField('[函数名(参数列表)]，请用“sample_rate”表示采样率的数值，系统会自动替换形参的语音文件采样率值')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(FPFE.id!=int(self.id.data),FPFE.name==field.data)}
        if FPFE.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的语音的前端处理器和特征提取器，请更名')
    
    def validate_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('fpfes')):
                target_fpfe_py=f'fpfes/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_fpfe_py) in os.listdir('fpfes'):
                    raise ValidationError('语音的前端处理器和特征提取器模块重名')
                shutil.copy(field.data,target_fpfe_py)
                self.modulename.data=target_fpfe_py[:-3].replace('/','.')
            else:
                self.modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.modulename.data=self.modulename.data if not self.modulename.data.endswith('.py') else self.modulename.data[:-3]
        else:
            self.modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.modulename.data=self.modulename.data.replace('/','.')
        if len(self.modulename.data)>50:
            raise ValidationError('语音的前端处理器和特征提取器模块名不能超过50个字符')

class DataPreprocessorForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,20)])
    modulename=StringField('模块名，如果要填写文件路径，请用“/”分隔',validators=[InputRequired()])
    classname=StringField('模块的全局函数名',validators=[InputRequired(),Length(1,50)])
    parameters=TextAreaField('全局函数的参数列表')
    attribute=TextAreaField('[函数名(参数列表)]')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(DataPreprocessor.id!=int(self.id.data),DataPreprocessor.name==field.data)}
        if DataPreprocessor.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的数据预处理器，请更名')
    
    def validate_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('data_preprocessors')):
                target_dp_py=f'data_preprocessors/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_dp_py) in os.listdir('data_preprocessors'):
                    raise ValidationError('数据预处理器模块重名')
                shutil.copy(field.data,target_dp_py)
                self.modulename.data=target_dp_py[:-3].replace('/','.')
            else:
                self.modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.modulename.data=self.modulename.data if not self.modulename.data.endswith('.py') else self.modulename.data[:-3]
        else:
            self.modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.modulename.data=self.modulename.data.replace('/','.')
        if len(self.modulename.data)>50:
            raise ValidationError('数据预处理器模块名不能超过50个字符')

class AcousticModelForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,20)])
    note=TextAreaField('备注')
    typ=SelectField('类型',choices=[('0','深度学习'),('1','深度神经网络+隐马尔科夫模型'),('2','高斯混合模型+隐马尔科夫模型')])
    content=SelectField('内容',choices=[('0','结构'),('1','结构+权重')])
    file=TextAreaField('文件路径，请用“/”分隔',validators=[InputRequired()]) # 表示由深度学习框架保存而来的文件路径或源文件路径，也可以写源代码文件的相对导入路径，之后会转换为modulename的内容
    filetype=SelectField('文件类型',choices=[('0','源代码'),('1','深度学习框架导出的模型文件')])
    weights_file=TextAreaField('权重文件路径')
    weights_type=SelectField('权重文件类型',choices=[('-1',''),('0','keras权重类型'),('1','keras检查点类型'),('2','pytorch字典类型')])
    modulename=HiddenField('源代码模块名')
    classname=StringField('源代码中的模块的全局函数名',validators=[Length(max=50)])
    parameters=TextAreaField('源代码中的全局函数的参数列表')
    attribute=TextAreaField('源代码中的[函数名(参数列表)]')
    file_ext=SelectField('深度学习框架导出的结构+权重模型文件的加载方式',choices=[('-1',''),('0','saved_model格式'),('1','json格式'),('2','config格式'),('3','pytorch模型格式')])
    framework=SelectField('所使用的深度学习框架',choices=[('-1',''),('0','tf.keras'),('1','torch.nn'),('2','transformers.Wav2Vec2ForCTC')])
    infer_classname=StringField('推理模型所用的源代码的模块的全局函数名（在模型的源代码文件中找这个模块的全局函数名）')
    infer_parameters=TextAreaField('推理模型所用的源代码的全局函数的参数列表')
    infer_attribute=TextAreaField('推理模型所用的源代码的[函数名(参数列表)]')
    infer_input_layer_names=TextAreaField('使用TensorFlow框架的推理模型的输入节点名，请用“,”分隔各输入节点名')
    infer_output_layer_names=TextAreaField('使用TensorFlow框架的推理模型的输出节点名，请用“,”分隔各输出节点名')
    optimizer_modulename=StringField('（模型参数）优化器所在的模块名，如果要填写文件路径，请用“/”分隔')
    optimizer_classname=StringField('实现（模型参数）优化器的模块的全局函数名')
    optimizer_parameters=TextAreaField('实现（模型参数）优化器的全局函数的参数列表')
    optimizer_attribute=TextAreaField('实现（模型参数）优化器的[函数名(参数列表)]')
    loss_modulename=StringField('（模型）损失函数对象获取函数所在的模块名，如果要填写文件路径，请用“/”分隔')
    loss_classname=StringField('（模型）损失函数对象获取函数所用的模块的全局函数名')
    loss_parameters=TextAreaField('（模型）损失计算函数对象获取函数所用的全局函数的参数列表')
    loss_attribute=TextAreaField('（模型）损失函数对象获取函数所用的[函数名(参数列表)]')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(AcousticModel.id!=int(self.id.data),AcousticModel.name==field.data)}
        if AcousticModel.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的声学模型，请更名')
    
    def validate_file(self,field):
        if self.filetype.data=='0':
            if os.path.exists(field.data):
                if not os.path.abspath(field.data).startswith(os.path.abspath('acoustic_models')):
                    target_acoustic_model_py=f'acoustic_models/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                    if os.path.basename(target_acoustic_model_py) in os.listdir('acoustic_models'):
                        raise ValidationError('声学模型模块重名')
                    shutil.copy(field.data,target_acoustic_model_py)
                    self.modulename.data=target_acoustic_model_py[:-3].replace('/','.')
                else:
                    self.modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                    self.modulename.data=self.modulename.data if not self.modulename.data.endswith('.py') else self.modulename.data[:-3]
            elif field.data=='':
                raise ValidationError('源代码类型的声学模型的模块名不能为空')
            else:
                self.modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
                self.modulename.data=self.modulename.data.replace('/','.')
            if len(self.modulename.data)>50:
                raise ValidationError('声学模型模块名不能超过50个字符')

    def validate_optimizer_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('optimizers')):
                target_optimizer_py=f'optimizers/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_optimizer_py) in os.listdir('optimizers'):
                    raise ValidationError('（模型参数）优化器模块重名')
                shutil.copy(field.data,target_optimizer_py)
                self.optimizer_modulename.data=target_optimizer_py[:-3].replace('/','.')
            else:
                self.optimizer_modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.optimizer_modulename.data=self.optimizer_modulename.data if not self.optimizer_modulename.data.endswith('.py') else self.optimizer_modulename.data[:-3]
        elif field.data!='':
            self.optimizer_modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.optimizer_modulename.data=self.optimizer_modulename.data.replace('/','.')
        if len(self.optimizer_modulename.data)>50:
            raise ValidationError('（模型参数）优化器的模块名不能超过50个字符')

    def validate_loss_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('losses')):
                target_loss_py=f'losses/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_loss_py) in os.listdir('losses'):
                    raise ValidationError('（模型）损失函数模块重名')
                shutil.copy(field.data,target_loss_py)
                self.loss_modulename.data=target_loss_py[:-3].replace('/','.')
            else:
                self.loss_modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.loss_modulename.data=self.loss_modulename.data if not self.loss_modulename.data.endswith('.py') else self.loss_modulename.data[:-3]
        elif field.data!='':
            self.loss_modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.loss_modulename.data=self.loss_modulename.data.replace('/','.')
        if len(self.loss_modulename.data)>50:
            raise ValidationError('（模型）损失函数的模块名不能超过50个字符')

    def validate_infer_input_layer_names(self,field):
        if (field.data!='' and self.infer_output_layer_names.data=='') or (field.data=='' and self.infer_output_layer_names.data!=''):
            raise ValidationError('模型推理时的输入节点名和输出节点名需要成对出现，以便获取推理所使用的网络路径')

    def validate_weights_file(self,field):
        if field.data!='':
            if not os.path.exists(field.data):
                raise ValidationError('模型的权重文件不存在')
            if int(self.weights_type.data)<0:
                raise ValidationError('请指明模型的权重文件的类型')
        
class LexiconDictForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,20)])
    g2p_modulename=StringField('字素-音素转换的模块名')
    g2p_classname=StringField('字素-音素转换的模块的全局函数名')
    g2p_parameters=TextAreaField('字素-音素转换的全局函数的参数列表')
    g2p_attribute=TextAreaField('字素-音素转换的[函数名(参数列表)]')
    g2p_dict_file=TextAreaField('字素-音素转换所用的字典的文件路径')
    g2p_dict_modulename=HiddenField('获取字素-音素转换所用的字典的模块名')
    g2p_dict_classname=StringField('获取字素-音素转换所用的字典的模块的全局函数名')
    g2p_dict_parameters=TextAreaField('获取字素-音素转换所用的字典的全局函数的参数列表')
    g2p_dict_attribute=TextAreaField('获取字素-音素转换所用的字典的[函数名(参数列表)]')
    penc_modulename=StringField('编码音素的模块名')
    penc_classname=StringField('编码音素的模块的全局函数名')
    penc_parameters=TextAreaField('编码音素的全局函数的参数列表')
    penc_attribute=TextAreaField('编码音素的[函数名(参数列表)]')
    penc_dict_file=TextAreaField('编码音素所用的字典的文件路径')
    penc_dict_modulename=HiddenField('获取编码音素所用的字典的模块名')
    penc_dict_classname=StringField('获取编码音素所用的字典的模块的全局函数名')
    penc_dict_parameters=TextAreaField('获取编码音素所用的字典的全局函数的参数列表')
    penc_dict_attribute=TextAreaField('获取编码音素所用的字典的[函数名(参数列表)]')
    pdec_modulename=StringField('解码音素的模块名')
    pdec_classname=StringField('解码音素的模块的全局函数名')
    pdec_parameters=TextAreaField('解码音素的全局函数的参数列表')
    pdec_attribute=TextAreaField('解码音素的[函数名(参数列表)]')
    pdec_dict_file=TextAreaField('解码音素所用的字典的文件路径')
    pdec_dict_modulename=HiddenField('获取解码音素所用的字典的模块名')
    pdec_dict_classname=StringField('获取解码音素所用的字典的模块的全局函数名')
    pdec_dict_parameters=TextAreaField('获取解码音素所用的字典的全局函数的参数列表')
    pdec_dict_attribute=TextAreaField('获取解码音素所用的字典的[函数名(参数列表)]')
    p2g_modulename=StringField('音素-字素转换的模块名')
    p2g_classname=StringField('音素-字素转换的模块的全局函数名')
    p2g_parameters=TextAreaField('音素-字素转换的全局函数的参数列表')
    p2g_attribute=TextAreaField('音素-字素转换的[函数名(参数列表)]')
    p2g_dict_file=TextAreaField('音素-字素转换所用的字典的文件路径')
    p2g_dict_modulename=HiddenField('获取音素-字素转换所用的字典的模块名')
    p2g_dict_classname=StringField('获取音素-字素转换所用的字典的模块的全局函数名')
    p2g_dict_parameters=TextAreaField('获取音素-字素转换所用的字典的全局函数的参数列表')
    p2g_dict_attribute=TextAreaField('获取音素-字素转换所用的字典的[函数名(参数列表)]')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(LexiconDict.id!=int(self.id.data),LexiconDict.name==field.data)}
        if LexiconDict.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的发音字典，请更名')
    
    def validate_g2p_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                target_g2p_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_g2p_py) in os.listdir('lexicon_dicts'):
                    raise ValidationError('字素-音素转换模块重名')
                shutil.copy(field.data,target_g2p_py)
                self.g2p_modulename.data=target_g2p_py[:-3].replace('/','.')
            else:
                self.g2p_modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.g2p_modulename.data=self.g2p_modulename.data if not self.g2p_modulename.data.endswith('.py') else self.g2p_modulename.data[:-3]
        else:
            self.g2p_modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.g2p_modulename.data=self.g2p_modulename.data.replace('/','.')
        if len(self.g2p_modulename.data)>50:
            raise ValidationError('字素-音素转换模块名不能超过50个字符')
    
    def validate_g2p_dict_file(self,field):
        if field.data.endswith('.py'):
            if os.path.exists(field.data):
                if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                    target_g2p_dict_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                    if os.path.basename(target_g2p_dict_py) in os.listdir('lexicon_dicts'):
                        raise ValidationError('获取字素-音素转换所用的字典模块重名')
                    shutil.copy(field.data,target_g2p_dict_py)
                    self.g2p_dict_modulename.data=target_g2p_dict_py[:-3].replace('/','.')
                else:
                    self.g2p_dict_modulename.data=os.path.relpath(field.data,'.')[:-3].replace(path_sep,'.')
            else:
                raise ValidationError(f'{field.data}不存在')
        elif field.data!='' or not field.data.endswith(('.txt','.json')):
            self.g2p_dict_modulename.data=field.data.replace('/','.')
        else:
            self.g2p_dict_modulename.data=''
        if len(self.g2p_dict_modulename.data)>50:
            raise ValidationError('获取字素-音素转换所用的字典的模块名不能超过50个字符')
        
    def validate_penc_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                target_penc_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_penc_py) in os.listdir('lexicon_dicts'):
                    raise ValidationError('音素编码模块重名')
                shutil.copy(field.data,target_penc_py)
                self.penc_modulename.data=target_penc_py[:-3].replace('/','.')
            else:
                self.penc_modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.penc_modulename.data=self.penc_modulename.data if not self.penc_modulename.data.endswith('.py') else self.penc_modulename.data[:-3]
        else:
            self.penc_modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.penc_modulename.data=self.penc_modulename.data.replace('/','.')
        if len(self.penc_modulename.data)>50:
            raise ValidationError('音素编码模块名不能超过50个字符')
    
    def validate_penc_dict_file(self,field):
        if field.data.endswith('.py'):
            if os.path.exists(field.data):
                if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                    target_penc_dict_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                    if os.path.basename(target_penc_dict_py) in os.listdir('lexicon_dicts'):
                        raise ValidationError('获取音素编码所用的字典模块重名')
                    shutil.copy(field.data,target_penc_dict_py)
                    self.penc_dict_modulename.data=target_penc_dict_py[:-3].replace('/','.')
                else:
                    self.penc_dict_modulename.data=os.path.relpath(field.data,'.')[:-3].replace(path_sep,'.')
            else:
                raise ValidationError(f'{field.data}不存在')
        elif field.data!='' or not field.data.endswith(('.txt','.json')):
            self.penc_dict_modulename.data=field.data.replace('/','.')
        else:
            self.penc_dict_modulename.data=''
        if len(self.penc_dict_modulename.data)>50:
            raise ValidationError('获取音速编码所用的字典的模块名不能超过50个字符')
        
    def validate_pdec_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                target_pdec_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_pdec_py) in os.listdir('lexicon_dicts'):
                    raise ValidationError('音素解码模块重名')
                shutil.copy(field.data,target_pdec_py)
                self.pdec_modulename.data=target_pdec_py[:-3].replace('/','.')
            else:
                self.pdec_modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.pdec_modulename.data=self.pdec_modulename.data if not self.pdec_modulename.data.endswith('.py') else self.pdec_modulename.data[:-3]
        else:
            self.pdec_modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.pdec_modulename.data=self.pdec_modulename.data.replace('/','.')
        if len(self.pdec_modulename.data)>50:
            raise ValidationError('音素解码模块名不能超过50个字符')
    
    def validate_pdec_dict_file(self,field):
        if field.data.endswith('.py'):
            if os.path.exists(field.data):
                if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                    target_pdec_dict_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                    if os.path.basename(target_pdec_dict_py) in os.listdir('lexicon_dicts'):
                        raise ValidationError('获取音素解码所用的字典模块重名')
                    shutil.copy(field.data,target_pdec_dict_py)
                    self.pdec_dict_modulename.data=target_pdec_dict_py[:-3].replace('/','.')
                else:
                    self.pdec_dict_modulename.data=os.path.relpath(field.data,'.')[:-3].replace(path_sep,'.')
            else:
                raise ValidationError(f'{field.data}不存在')
        elif field.data!='' or not field.data.endswith(('.txt','.json')):
            self.pdec_dict_modulename.data=field.data.replace('/','.')
        else:
            self.pdec_dict_modulename.data=''
        if len(self.pdec_dict_modulename.data)>50:
            raise ValidationError('获取音素解码所用的字典的模块名不能超过50个字符')
        
    def validate_p2g_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                target_p2g_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_p2g_py) in os.listdir('lexicon_dicts'):
                    raise ValidationError('音素-字素转换模块重名')
                shutil.copy(field.data,target_p2g_py)
                self.p2g_modulename.data=target_p2g_py[:-3].replace('/','.')
            else:
                self.p2g_modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.p2g_modulename.data=self.p2g_modulename.data if not self.p2g_modulename.data.endswith('.py') else self.p2g_modulename.data[:-3]
        else:
            self.p2g_modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.p2g_modulename.data=self.p2g_modulename.data.replace('/','.')
        if len(self.p2g_modulename.data)>50:
            raise ValidationError('音素-字素转换模块名不能超过50个字符')
    
    def validate_p2g_dict_file(self,field):
        if field.data.endswith('.py'):
            if os.path.exists(field.data):
                if not os.path.abspath(field.data).startswith(os.path.abspath('lexicon_dicts')):
                    target_p2g_dict_py=f'lexicon_dicts/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                    if os.path.basename(target_p2g_dict_py) in os.listdir('lexicon_dicts'):
                        raise ValidationError('获取音素-字素转换所用的字典模块重名')
                    shutil.copy(field.data,target_p2g_dict_py)
                    self.p2g_dict_modulename.data=target_p2g_dict_py[:-3].replace('/','.')
                else:
                    self.p2g_dict_modulename.data=os.path.relpath(field.data,'.')[:-3].replace(path_sep,'.')
            else:
                raise ValidationError(f'{field.data}不存在')
        elif field.data!='' or not field.data.endswith(('.txt','.json')):
            self.p2g_dict_modulename.data=field.data.replace('/','.')
        else:
            self.p2g_dict_modulename.data=''
        if len(self.p2g_dict_modulename.data)>50:
            raise ValidationError('获取音素-字素转换所用的字典的模块名不能超过50个字符')
        
class LanguageModelForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,20)])
    typ=SelectField('类型',choices=[('0','n元语法'),('1','深度学习')])
    content=SelectField('内容',choices=[('0','结构'),('1','结构+权重')])
    file=TextAreaField('文件路径，请用“/”分隔',validators=[InputRequired()])
    filetype=SelectField('文件类型',choices=[('0','源代码文件'),('1','深度学习框架导出的模型文件')])
    weights_file=TextAreaField('权重文件路径')
    weights_type=SelectField('权重文件类型',choices=[('-1',''),('0','keras权重类型'),('1','keras检查点类型'),('2','pytorch字典类型')])
    file_ext=SelectField('机器学习框架导出的结构+权重模型文件的加载方式',choices=[('-1',''),('0','saved_model格式'),('1','json格式'),('2','config格式'),('3','pytorch模型格式')])
    framework=SelectField('所使用的机器学习框架',choices=[('-1',''),('0','tf.keras'),('1','torch.nn')])
    modulename=HiddenField('源代码的模块名')
    classname=StringField('源代码中的模块的全局函数名',validators=[Length(max=50)])
    parameters=TextAreaField('源代码中的全局函数的参数列表')
    attribute=TextAreaField('源代码中的[函数名(参数列表)]')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(LanguageModel.id!=int(self.id.data),LanguageModel.name==field.data)}
        if LanguageModel.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的语言模型，请更名')
    
    def validate_file(self,field):
        if self.filetype.data=='0':
            if os.path.exists(field.data):
                if not os.path.abspath(field.data).startswith(os.path.abspath('lm')):
                    target_lm_py=f'lm/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                    if os.path.basename(target_lm_py) in os.listdir('lm'):
                        raise ValidationError('语言模型模块重名')
                    shutil.copy(field.data,target_lm_py)
                    self.modulename.data=target_lm_py[:-3].replace('/','.')
                else:
                    self.modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                    self.modulename.data=self.modulename.data if not self.modulename.data.endswith('.py') else self.modulename.data[:-3]
            else:
                self.modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
                self.modulename.data=self.modulename.data.replace('/','.')
            if len(self.modulename.data)>50:
                raise ValidationError('语言模型模块名不能超过50个字符，请检查你添加的源代码文件相对于项目主目录的路径名的长度')

    def validate_weights_file(self,field):
        if field.data!='':
            if not os.path.exists(field.data):
                raise ValidationError('模型的权重文件不存在')
            if int(self.weights_type.data)<0:
                raise ValidationError('请指明模型的权重文件的类型')
            
class DecoderForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,20)])
    modulename=StringField('模块名，如果要填写文件路径，请用“/”分隔',validators=[InputRequired()])
    classname=StringField('模块的全局函数名',validators=[InputRequired(),Length(1,50)])
    parameters=TextAreaField('全局函数的参数列表')
    attribute=TextAreaField('[函数名(参数列表)]')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(Decoder.id!=int(self.id.data),Decoder.name==field.data)}
        if Decoder.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的解码器，请更名')
    
    def validate_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('decoders')):
                target_decoder_py=f'decoders/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_decoder_py) in os.listdir('decoders'):
                    raise ValidationError('解码器模块重名')
                shutil.copy(field.data,target_decoder_py)
                self.modulename.data=target_decoder_py[:-3].replace('/','.')
            else:
                self.modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.modulename.data=self.modulename.data if not self.modulename.data.endswith('.py') else self.modulename.data[:-3]
        else:
            self.modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.modulename.data=self.modulename.data.replace('/','.')
        if len(self.modulename.data)>50:
            raise ValidationError('解码器模块名不能超过50个字符，请检查解码器源代码文件的位置相对于项目主目录的路径名的长度')
        
class PostProcessorForm(FlaskForm):
    id=HiddenField('id')
    name=StringField('名称',validators=[InputRequired(),Length(1,20)])
    modulename=StringField('模块名，如果要填写文件路径，请用“/”分隔',validators=[InputRequired()])
    classname=StringField('模块的全局函数名',validators=[InputRequired(),Length(1,50)])
    parameters=TextAreaField('全局函数的参数列表')
    attribute=TextAreaField('[函数名(参数列表)]')
    submit=SubmitField('保存')

    def validate_name(self,field):
        filter={and_(PostProcessor.id!=int(self.id.data),PostProcessor.name==field.data)}
        if PostProcessor.query.filter(*filter).first():
            raise ValidationError('已存在相同名称的后处理器，请更名')
    
    def validate_modulename(self,field):
        if os.path.exists(field.data):
            if not os.path.abspath(field.data).startswith(os.path.abspath('post_processors')):
                target_pp_py=f'post_processors/{os.path.splitext(field.data.split("/")[-1])[0]}.py'
                if os.path.basename(target_pp_py) in os.listdir('post_processors'):
                    raise ValidationError('后处理器模块重名')
                shutil.copy(field.data,target_pp_py)
                self.modulename.data=target_pp_py[:-3].replace('/','.')
            else:
                self.modulename.data=os.path.relpath(field.data,'.').replace(path_sep,'.')
                self.modulename.data=self.modulename.data if not self.modulename.data.endswith('.py') else self.modulename.data[:-3]
        else:
            self.modulename.data=field.data if not field.data.endswith('.py') else field.data[:-3]
            self.modulename.data=self.modulename.data.replace('/','.')
        if len(self.modulename.data)>50:
            raise ValidationError('后处理器模块名不能超过50个字符，请检查后处理器源代码文件的位置相对于项目主目录的路径名的长度')
        
class ConfigForm(FlaskForm):
    python_lib_dir=TextAreaField('python库所在的目录')
    dataset_source_dir=TextAreaField('数据集集中所在的目录')
    ip=StringField('系统ip',validators=[InputRequired(),Length(7,15),Regexp('^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$|^localhost$',0,'IP地址非法')])
    port=IntegerField('系统端口号',validators=[InputRequired()])
    host_max_available_mem=IntegerField('系统最大的可用内存空间',validators=[InputRequired()])
    keras_model_type_s=TextAreaField('基于TensorFlow的模型对象所属的类名')
    pytorch_model_type_s=TextAreaField('基于Pytorch的模型对象所属的类名')
    submit=SubmitField('保存')

class DireForm(FlaskForm):
    value=TextAreaField('需处理的目录路径')
    submit=SubmitField('保存')

    def validate_value(self,field):
        if not os.path.exists(field.data):
            raise ValidationError(f'{field.data}目录不存在！')